package org.offsync.rich

import java.nio.channels.SelectableChannel
import java.nio.channels.Selector
import java.nio.channels.SelectionKey
import org.offsync.old.async.SelectionAttachment
import org.offsync.util.NoOp
import org.offsync.old.async.AsyncReturn
import org.offsync.old.async.AsyncService

class InterestOpsAlreadyRegistedException extends Exception

class RichSelectableChannel[T <: SelectableChannel](val channel: T) {
  def onAcceptable(selector: AsyncService)(f: () => Unit): Unit = onReady(selector, SelectionKey.OP_ACCEPT)(f)
  def onConnectable(selector: AsyncService)(f: () => Unit): Unit = onReady(selector, SelectionKey.OP_CONNECT)(f)
  def onReadable(selector: AsyncService)(f: () => Unit): Unit = onReady(selector, SelectionKey.OP_READ)(f)
  def onWritable(selector: AsyncService)(f: () => Unit): Unit = onReady(selector, SelectionKey.OP_WRITE)(f)
  
  def onReady(selector: AsyncService, op: Int)(f: () => Unit): Unit = {
    val opHandler = { () =>
      val postKey = channel.keyFor(selector.selector)
      val postOp = postKey.interestOps & ~op
      val postAttachment = postKey.attachment.asInstanceOf[SelectionAttachment]
      if (postOp == 0) {
        postKey.cancel()
      } else {
        postKey.interestOps(postOp)
        postKey.attach(postAttachment.withOp(postOp)(NoOp))
      }
      f()
    }
    selector.invoke { () =>
      val key = channel.keyFor(selector.selector)
      if (key == null) {
        val attachment = SelectionAttachment.withOp(op)(opHandler)
        channel.register(selector.selector, op, attachment)
      } else {
        val ops = key.interestOps
        if ((ops & op) != 0) {
          throw new InterestOpsAlreadyRegistedException()
        }
        val oldAttachment = key.attachment.asInstanceOf[SelectionAttachment]
        val newAttachment = oldAttachment.withOp(op)(opHandler)
        key.interestOps(ops | op)
        key.attach(newAttachment)
      }
      AsyncReturn
    }
  }
}
